#!/bin/bash
#
# Power Control tool
#   Enable/disable AC relay
#   On/off System by step moter to press power key

export PATH=$PATH:/usr/sbin:/usr/libexec

# shellcheck source=meta-facebook/meta-bletchley/recipes-bletchley/plat-tools/files/bletchley-common-functions
source /usr/libexec/bletchley-common-functions

DELAY_POWER_ON="0.5"
DELAY_POWER_OFF="10"
DELAY_POWER_RECOVERY_MODE="10"
POWER_BTN_TIMEOUT_SEC=10

REV_EVT="EVT"
REV_DVT="DVT"
REV_UNKNOW="UNKNOW"

DBUS_HOST_ST_ON="xyz.openbmc_project.State.Host.HostState.Running"
DBUS_HOST_ST_OFF="xyz.openbmc_project.State.Host.HostState.Off"

HOST_ST_UNKNOW="Unknow"
HOST_ST_ON="On"
HOST_ST_OFF="Off"
HOST_ST_SLEEP="Sleep"
HOST_ST_DFU="DFU"
HOST_ST_RECOVERY="Recovery"
HOST_AC_ON="AC On"
HOST_AC_OFF="AC Off"

ACTION_ON="on"
ACTION_OFF="off"
ACTION_DFU="dfu"
ACTION_RECOVERY="recovery"
ACTION_CYCLE="cycle"
ACTION_RESET="reset"
ACTION_AC_ON="ac-on"
ACTION_AC_OFF="ac-off"
ACTION_STATUS="status"
ACTION_BOOT_MODE="boot-from-bootmode"

VALID_SLED_ACTIONS="
    $ACTION_ON
    $ACTION_OFF
    $ACTION_AC_ON
    $ACTION_AC_OFF
    $ACTION_STATUS
    $ACTION_DFU
    $ACTION_RECOVERY
    $ACTION_CYCLE
    $ACTION_RESET
    $ACTION_BOOT_MODE
"

BOOT_MODE_REGULAR="\"xyz.openbmc_project.Control.Boot.Mode.Modes.Regular\""
BOOT_MODE_SAFE="\"xyz.openbmc_project.Control.Boot.Mode.Modes.Safe\""
BOOT_MODE_SETUP="\"xyz.openbmc_project.Control.Boot.Mode.Modes.Setup\""

function is_valid_sled_action()
{
    local ACTION=$1
    for i in $VALID_SLED_ACTIONS
    do
        if [ "$i" = "$ACTION" ]; then
            return 0
        fi
    done
    return 1
}

function get_board_rev()
{
    local rev_id0
    local rev_id1
    local rev_id2
    local rev_val

    rev_id0=$(get_gpio "REV_ID0")
    rev_id1=$(get_gpio "REV_ID1")
    rev_id2=$(get_gpio "REV_ID2")
    rev_val=$((rev_id0+(rev_id1<<1)+(rev_id2<<2)))

    case $rev_val in
        0)
            echo "$REV_EVT"
            ;;
        1)
            echo "$REV_DVT"
            ;;
        *)
            echo "$REV_UNKNOW"
            return 1
            ;;
    esac

    return 0
}

function trigger_power_button()
{
    local sled_num=$1
    local delay_time=$2

    #SLED{N}_MS_DETECT1  (initial position)
    GPIO_DETECT_PIN1="SLED${sled_num}_MS_DETECT1"
    #SLED{N}_MS_DETECT0  (MAC position)
    GPIO_DETECT_PIN0="SLED${sled_num}_MS_DETECT0"

    echo "Motor go forward to press Power key"
    motor-ctrl "sled${sled_num}" f >/dev/null
    wait_gpio_falling "${GPIO_DETECT_PIN0}" "$POWER_BTN_TIMEOUT_SEC"
    motor-ctrl "sled${sled_num}" s >/dev/null

    if [ "$(get_gpio  "$GPIO_DETECT_PIN0")" -eq 0 ];then
        echo "Power key switch triggered"
        echo "Press power key for Sled${1} ${delay_time} seconds..."
        sleep "$delay_time"
    else
        echo "Power key switch not trigger, back motor to initial position"
    fi

    motor-ctrl "sled${sled_num}" r >/dev/null
    wait_gpio_falling "${GPIO_DETECT_PIN1}" "$POWER_BTN_TIMEOUT_SEC"
    motor-ctrl "sled${sled_num}" s >/dev/null
    if [ "$(get_gpio  "$GPIO_DETECT_PIN1")" -eq 0 ];then
        echo "Motor reverse to initial position successful"
    else
        echo "Initial position switch not trigger, force stop motor"
    fi
}

function release_power_button()
{
    local sled_num=$1
    GPIO_DETECT_PIN1="SLED${sled_num}_MS_DETECT1"

    if [ "$(get_gpio  "$GPIO_DETECT_PIN1")" -eq 0 ]; then
        echo "Motor at initial position already"
        return 0
    fi

    motor-ctrl "sled${sled_num}" r >/dev/null
    wait_gpio_falling "${GPIO_DETECT_PIN1}" "$POWER_BTN_TIMEOUT_SEC"
    motor-ctrl "sled${sled_num}" s >/dev/null
    if [ "$(get_gpio  "$GPIO_DETECT_PIN1")" -eq 0 ];then
        echo "Motor reverse to initial position successful"
        return 0
    fi

    echo "Error: Initial position switch not trigger"
    return 1
}

function press_power_button()
{
    local sled_num=$1

    GPIO_DETECT_PIN0="SLED${sled_num}_MS_DETECT0"

    echo "Motor go forward to press Power button"
    motor-ctrl "sled${sled_num}" f >/dev/null
    wait_gpio_falling "${GPIO_DETECT_PIN0}" "$POWER_BTN_TIMEOUT_SEC"
    motor-ctrl "sled${sled_num}" s >/dev/null

    if [ "$(get_gpio  "$GPIO_DETECT_PIN0")" -eq 0 ];then
        echo "Power button switch triggered"
        return 0
    fi

    echo "Error: Power button switch not trigger"
    return 1
}

#Get i2c bus number for sledN
function get_bus_num()
{
    SLED_NUM=$1
    local bus=0
    #Mapping Sled number 1~6 to i2c bus number 0~5
    if [[ "$SLED_NUM" = [1-6] ]]; then
        bus=$(( SLED_NUM - 1 ))
    fi
    echo "$bus"
}

function get_ac_status()
{
    i2c_bus=$(get_bus_num "$1")
    p1_output_reg=$(i2cget -f -y "$i2c_bus" 0x76 0x03)
    p1_config_reg=$(i2cget -f -y "$i2c_bus" 0x76 0x07)
    host_pwr="$(( (p1_output_reg & 0x80)>>7 ))"
    is_output="$(( (~p1_config_reg & 0x80)>>7 ))"

    if [ "$(( host_pwr & is_output ))" -eq 1 ];then
        echo "$HOST_AC_ON"
    else
        echo "$HOST_AC_OFF"
    fi
}

function get_host_status_dbus()
{
    local sled_num=$1
    local object="/xyz/openbmc_project/state/host${sled_num}"
    local service="xyz.openbmc_project.State.Host${sled_num}"
    local interface="xyz.openbmc_project.State.Host"
    local property="CurrentHostState"
    local host_state

    host_state=$(busctl get-property "$service" "$object" "$interface" "$property" | cut -d '"' -f2)

    if [ "$host_state" = "$DBUS_HOST_ST_ON" ]; then
        echo "$HOST_ST_ON"
    elif [ "$host_state" = "$DBUS_HOST_ST_OFF" ]; then
        echo "$HOST_ST_OFF"
    else
        echo "$HOST_ST_UNKNOW"
        return 1
    fi

    return 0
}

function get_addr_from_dts_aliases()
{
    local node_address
    node_address=$(awk -F '@' '{printf $2}' /sys/firmware/devicetree/base/aliases/"$1")
    echo "$node_address"
}

function get_host_status_mdio()
{
    local SLED_NUM=$1
    local MDIO_BUS

    MDIO_BUS="$(get_addr_from_dts_aliases mdio0).mdio-1"

    declare -a PORT_MAP=(0 3 2 1 7 6 5)

    # check /dev/mem
    if ! create_dev_mem; then
        return 1
    fi

    local CHECK_CNT=0
    local MDIO_ERR_CNT=0
    local CUR_HOST_ST=$HOST_ST_UNKNOW
    local SLED_LAST_ACTION

    if [ -f /tmp/sled"${SLED_NUM}"-last-action ]; then
        SLED_LAST_ACTION=$(cat /tmp/sled"${SLED_NUM}"-last-action)
    fi

    while true
    do
        if POST_ST_VAL=$(mdio "$MDIO_BUS" phy "${PORT_MAP[SLED_NUM]}" 0); then
            if [ $((POST_ST_VAL&16#0800)) -eq $((16#0000)) ]; then
                case $SLED_LAST_ACTION in
                    "$ACTION_DFU")
                        TMP_HOST_ST="$HOST_ST_DFU"
                        ;;
                    *)
                        TMP_HOST_ST="$HOST_ST_OFF"
                        ;;
                esac
            elif [ $((POST_ST_VAL&16#0A00)) -eq $((16#0A00)) ]; then
                TMP_HOST_ST="$HOST_ST_ON"
                case $SLED_LAST_ACTION in
                    "$ACTION_RECOVERY")
                        TMP_HOST_ST="$HOST_ST_RECOVERY"
                        ;;
                    *)
                        TMP_HOST_ST="$HOST_ST_ON"
                        ;;
                esac
            elif [ $((POST_ST_VAL&16#0900)) -eq $((16#0900)) ]; then
                TMP_HOST_ST="$HOST_ST_SLEEP"
            else
                TMP_HOST_ST="$HOST_ST_UNKNOW"
            fi

            if [ "$CUR_HOST_ST" == "$TMP_HOST_ST" ]; then
                CHECK_CNT=$((CHECK_CNT+1))
            else
                CUR_HOST_ST=$TMP_HOST_ST
                CHECK_CNT=0
            fi

            if [ "$CHECK_CNT" -ge 5 ]; then
                echo "$CUR_HOST_ST"
                break
            fi
        else
            MDIO_ERR_CNT=$((MDIO_ERR_CNT+1))
            if [ "$MDIO_ERR_CNT" -ge 5 ]; then
                echo "$HOST_ST_UNKNOW"
                return 1
            fi
        fi
    done

    return 0
}

function get_host_status()
{
    local sled_num=$1

    if [ "$(get_ac_status "$SLED_NUM")" == "$HOST_AC_OFF" ];then
        echo "$HOST_AC_OFF"
        return 0
    fi

    if [ "$(get_board_rev)" = "$REV_EVT" ]; then
        get_host_status_dbus "$sled_num"
    else
        get_host_status_mdio "$sled_num"
    fi
    return $?
}

function get_host_bootmode()
{
    local BUS_NAME="xyz.openbmc_project.Settings"
    local OBJ_PATH="/xyz/openbmc_project/control/host${1}/boot"
    local INTF_NAME="xyz.openbmc_project.Control.Boot.Mode"
    busctl get-property "${BUS_NAME}" "${OBJ_PATH}" "${INTF_NAME}" BootMode | awk '{print $2}'
}

function do_action_reset()
{
    # 1. Power off
    # 2. Power on

    local SLED_NUM=$1
    local CUR_ST=$2

    if [ "$CUR_ST" != "$HOST_ST_OFF" ]; then
        do_action_off "$SLED_NUM"
    else
        echo "sled${SLED_NUM}: already powered off"
    fi

    sleep 3
    do_action_on "$SLED_NUM"
}

function do_action_cycle()
{
    # 1. AC off
    # 2. AC on
    # 3. Power on

    local SLED_NUM=$1

    do_action_ac_off "$SLED_NUM"
    sleep 3
    do_action_ac_on "$SLED_NUM"
    sleep 3
    do_action_on "$SLED_NUM"
}

function do_action_ac_on()
{
    local SLED_NUM=$1
    echo "sled${SLED_NUM}: turn on AC"
    set_gpio "power-host${SLED_NUM}" 1
    echo "$ACTION_AC_ON" > "/tmp/sled${SLED_NUM}-last-action"
}

function do_action_ac_off()
{
    local SLED_NUM=$1
    echo "sled${SLED_NUM}: turn off AC"
    set_gpio "power-host${SLED_NUM}" 0
    echo "$ACTION_AC_OFF" > "/tmp/sled${SLED_NUM}-last-action"
}

function do_action_on()
{
    local SLED_NUM=$1
    echo "sled${SLED_NUM}: power on host"
    trigger_power_button  "$SLED_NUM"  "$DELAY_POWER_ON"
    sleep 10 # Mac mini need about 10 second to stable link status
    echo "$ACTION_ON" > "/tmp/sled${SLED_NUM}-last-action"
}

function do_action_off()
{
    local SLED_NUM=$1
    echo "sled${SLED_NUM}: power off host"
    trigger_power_button  "$SLED_NUM"  "$DELAY_POWER_OFF"
    echo "$ACTION_OFF" > "/tmp/sled${SLED_NUM}-last-action"
}

function do_action_recovery()
{
    local SLED_NUM=$1
    echo "sled${SLED_NUM}: trigger host recovery mode"
    trigger_power_button  "$SLED_NUM"  "$DELAY_POWER_RECOVERY_MODE"
    echo "$ACTION_RECOVERY" > "/tmp/sled${SLED_NUM}-last-action"
}

function do_action_dfu()
{
    local SLED_NUM=$1
    echo "sled${SLED_NUM}: trigger host dfu mode"

    # turn ac off, and hold for 25 seconds
    do_action_ac_off "$SLED_NUM"
    sleep 25

    # press power button
    echo "SLED$SLED_NUM: pressing power button"
    if ! press_power_button "$SLED_NUM"; then
        echo "SLED$SLED_NUM: press power button failed"
        echo "SLED$SLED_NUM: releasing power button"
        release_power_button "$SLED_NUM"
        return 1
    fi
    sleep 1

    # turn ac on
    echo "SLED$SLED_NUM: turn ac-on"
    do_action_ac_on "$SLED_NUM"
    sleep 3

    # release power button
    echo "SLED$SLED_NUM: releasing host power button"
    if ! release_power_button "$SLED_NUM"; then
        echo "SLED$SLED_NUM: release power button failed"
        return 1
    fi
    echo "$ACTION_DFU" > "/tmp/sled${SLED_NUM}-last-action"
}

function host_state_on_action_handler()
{
    local SLED_NUM=$1
    local ACTION=$2

    case $ACTION in
        "$ACTION_OFF")
            do_action_off "$SLED_NUM"
            ;;
        "$ACTION_AC_OFF")
            do_action_ac_off "$SLED_NUM"
            ;;
        "$ACTION_RESET")
            do_action_reset "$SLED_NUM" "$HOST_ST_ON"
            ;;
        "$ACTION_CYCLE")
            do_action_cycle "$SLED_NUM"
            ;;
        *)
            echo "Invalid action ($ACTION) for current host state (On)"
            return 1
            ;;
    esac
}

function host_state_sleep_action_handler()
{
    local SLED_NUM=$1
    local ACTION=$2

    case $ACTION in
        "$ACTION_ON")
            do_action_on "$SLED_NUM"
            ;;
        "$ACTION_OFF")
            do_action_off "$SLED_NUM"
            ;;
        "$ACTION_AC_OFF")
            do_action_ac_off "$SLED_NUM"
            ;;
        "$ACTION_RESET")
            do_action_reset "$SLED_NUM" "$HOST_ST_ON"
            ;;
        "$ACTION_CYCLE")
            do_action_cycle "$SLED_NUM"
            ;;
        *)
            echo "Invalid action ($ACTION) for current host state (Sleep)"
            return 1
            ;;
    esac
}

function host_state_off_action_handler()
{
    local SLED_NUM=$1
    local ACTION=$2

    case $ACTION in
        "$ACTION_ON")
            do_action_on "$SLED_NUM"
            ;;
        "$ACTION_RECOVERY")
            do_action_recovery "$SLED_NUM"
            ;;
        "$ACTION_DFU")
            do_action_dfu "$SLED_NUM"
            ;;
        "$ACTION_AC_OFF")
            do_action_ac_off "$SLED_NUM"
            ;;
        "$ACTION_RESET")
            do_action_reset "$SLED_NUM" "$HOST_ST_ON"
            ;;
        "$ACTION_CYCLE")
            do_action_cycle "$SLED_NUM"
            ;;
        *)
            echo "Invalid action ($ACTION) for current host state (Off)"
            return 1
            ;;
    esac
}

function host_state_ac_off_action_handler()
{
    local SLED_NUM=$1
    local ACTION=$2

    case $ACTION in
        "$ACTION_AC_ON")
            do_action_ac_on "$SLED_NUM"
            ;;
        "$ACTION_DFU")
            do_action_dfu "$SLED_NUM"
            ;;
        "$ACTION_AC_OFF")
            echo "sled${SLED_NUM}: already ac off"
            return 1
            ;;
        "$ACTION_CYCLE")
            do_action_reset "$SLED_NUM"
            ;;
        *)
            echo "Invalid action ($ACTION) for current host state (AC Off)"
            return 1
            ;;
    esac
}

function host_state_ac_on_action_handler()
{
    local SLED_NUM=$1
    local ACTION=$2

    case $ACTION in
        "$ACTION_AC_OFF")
            do_action_ac_off "$SLED_NUM"
            ;;
        "$ACTION_DFU")
            do_action_dfu "$SLED_NUM"
            ;;
        "$ACTION_CYCLE")
            do_action_cycle "$SLED_NUM"
            ;;
        *)
            echo "sled${SLED_NUM}: already ac on"
            return 1
            ;;
    esac
}

function host_state_recovery_action_handler()
{
    local SLED_NUM=$1
    local ACTION=$2

    case $ACTION in
        "$ACTION_OFF")
            do_action_off "$SLED_NUM"
            ;;
        "$ACTION_AC_OFF")
            do_action_ac_off "$SLED_NUM"
            ;;
        "$ACTION_RESET")
            do_action_reset "$SLED_NUM" "$HOST_ST_ON"
            ;;
        "$ACTION_CYCLE")
            do_action_cycle "$SLED_NUM"
            ;;
        *)
            echo "Invalid action ($ACTION) for current host state (Recovery)"
            return 1
            ;;
    esac
}

function host_state_dfu_action_handler()
{
    local SLED_NUM=$1
    local ACTION=$2

    case $ACTION in
        "$ACTION_AC_OFF")
            do_action_ac_off "$SLED_NUM"
            ;;
        "$ACTION_CYCLE")
            do_action_cycle "$SLED_NUM"
            ;;
        *)
            echo "Invalid action ($ACTION) for current host state (DFU)"
            return 1
            ;;
    esac
}

function create_dev_mem()
{
    CHECK_CNT=0
    while true
    do
        CHECK_CNT=$((CHECK_CNT+1))
        if [ -c /dev/mem ]; then
            # /dev/mem already exist
            return 0
        elif mknod /dev/mem c 1 1; then
            # mknod success
            return 0
        elif [ "$CHECK_CNT" -ge 5 ]; then
            break
        fi
        sleep 1
    done

    echo "create /dev/mem failed"
    return 1
}

function show_usage(){
    echo "Usage: power-ctrl [sled1 | sled2 | sled3 | sled4 | sled5 | sled6]  [$VALID_SLED_ACTIONS]"
    echo "       power-ctrl chassis-cycle"
}


if [ $# -eq 1 ]; then
    if [ "$1" = "chassis-cycle" ];then
        echo "chassis cycle...."
        i2cset -y -f 12 0x11 0xd9 c
        exit 0
    else
        echo "Invalid argument: [ $1 ]"
        show_usage
        exit  1;
    fi
fi

if [ $# -gt 2 ]; then
  echo "Too many arguments"
  show_usage
  exit  1;
fi

if [[ "$1" =~ ^(sled[1-6]{1})$ ]]; then
  SLED=$1
  ACTION=$2
  SLED_NUM=${SLED:4}
else
  echo "invalid sled name: ${1}"
  show_usage
  exit 1;
fi

#Check if sled is present
if ! is_sled_present "${SLED_NUM}"; then
    echo "${SLED} is not present!"
    exit 1
elif ! is_valid_sled_action "$ACTION"; then
    echo "Unknown action: $ACTION"
    show_usage
    exit 1
fi

if [ "$ACTION" = "$ACTION_AC_ON" ]; then
    if [ "$(get_ac_status "$SLED_NUM")" = "$HOST_AC_OFF" ]; then
        do_action_ac_on "$SLED_NUM"
    fi
elif [ "$ACTION" = "$ACTION_AC_OFF" ]; then
    if [ "$(get_ac_status "$SLED_NUM")" != "$HOST_AC_OFF" ]; then
        do_action_ac_off "$SLED_NUM"
    fi
elif [ "$ACTION" = "$ACTION_STATUS" ];then
    HOST_CURR_STATUS=$(get_host_status "$SLED_NUM")
    echo "$HOST_CURR_STATUS"
else
    HOST_CURR_STATUS=$(get_host_status "$SLED_NUM")

    if [ "$ACTION" = "$ACTION_BOOT_MODE" ]; then
        BOOT_MODE=$(get_host_bootmode "$SLED_NUM")
        case "$BOOT_MODE" in
        "$BOOT_MODE_REGULAR")
            echo "Boot mode: on (regular)"
            ACTION="$ACTION_ON"
            ;;
        "$BOOT_MODE_SAFE")
            echo "Boot mode: recovery (safe)"
            ACTION="$ACTION_RECOVERY"
            ;;
        "$BOOT_MODE_SETUP")
            echo "Boot mode: dfu (setup)"
            ACTION="$ACTION_DFU"
            ;;
        *)
            echo "Boot mode: unknow"
            ;;
        esac
    fi

    case $HOST_CURR_STATUS in
        "$HOST_AC_OFF")
            host_state_ac_off_action_handler "$SLED_NUM" "$ACTION"
            ;;
        "$HOST_AC_ON")
            host_state_ac_on_action_handler "$SLED_NUM" "$ACTION"
            ;;
        "$HOST_ST_OFF")
            host_state_off_action_handler "$SLED_NUM" "$ACTION"
            ;;
        "$HOST_ST_ON")
            host_state_on_action_handler "$SLED_NUM" "$ACTION"
            ;;
        "$HOST_ST_SLEEP")
            host_state_sleep_action_handler "$SLED_NUM" "$ACTION"
            ;;
        "$HOST_ST_DFU")
            host_state_dfu_action_handler "$SLED_NUM" "$ACTION"
            ;;
        "$HOST_ST_RECOVERY")
            host_state_recovery_action_handler "$SLED_NUM" "$ACTION"
            ;;
    esac
fi
